package com.example.luyao.faceserver;

import fi.iki.elonen.NanoHTTPD;

import android.content.Context;
import android.util.Log;

import com.iim.recognition.caffe.LoadLibraryModule;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class HttpServer extends NanoHTTPD {
    private static final String TAG = "HttpServer";
    private LoadLibraryModule loadLibraryModule;
    private static final int feature_length = 512;
    private static final int max_face_num = 10;
    private UserFeatureDB userFeatureDB;
    private Lock lock_user_feature = new ReentrantLock();
    List<Map<String, Object>> all_user_feature;
    Map<Integer, Map<String, Object>> feature_id_to_feature = new HashMap<>();

    public void closeDB() {userFeatureDB.close();};

    private void query_user_feature(){
        lock_user_feature.lock();
        all_user_feature = userFeatureDB.queryAllUserFeature();
        for(int i = 0; i < all_user_feature.size(); i++) {
            Map<String, Object> user_feature = all_user_feature.get(i);
            feature_id_to_feature.put((int)user_feature.get("feature_id"), user_feature);
            Log.e(TAG, "feature num: " + i + "/" + all_user_feature.size() + " " +
                    user_feature.get("feature_id") + " " + user_feature.get("name"));
        }
        lock_user_feature.unlock();
    }

    private List<Map<String, Object>> recognition_user_feature(int face_count, float[][] feature){
        List<Map<String, Object>> ret = new ArrayList<>();
        lock_user_feature.lock();
        for (int m = 0; m < face_count; m++) {
            float max_score = 0;
            int max_score_index = -1;
            for(int i = 0; i < all_user_feature.size(); i++) {
                float current_score = 0;
                float[] feature_db_item = (float[])all_user_feature.get(i).get("feature");
                for (int j = 0; j < feature_length; j++) {
                    current_score += (feature_db_item[j] * feature[m][j]);
                }
                if (current_score > max_score) {
                    max_score = current_score;
                    max_score_index = i;
                }
            }
            if (max_score >= 0.42) {
                Map<String, Object> tmp = all_user_feature.get(max_score_index);
                Map<String, Object> user_info = (Map<String, Object>)(((HashMap)tmp).clone());
                user_info.put("index", m);
                ret.add(user_info);
                Log.e(TAG, "recognition_user_feature " + max_score + " name " + user_info.get("name"));
            }
        }
        lock_user_feature.unlock();
        return ret;
    }

    public HttpServer(int port, Context context) throws IOException {
        super(port);
        loadLibraryModule = LoadLibraryModule.getInstance();
        userFeatureDB = new UserFeatureDB(context);
        query_user_feature();
    }

    JSONObject new_unknow_error_json(){
        JSONObject ret = new JSONObject();
        try {
            ret.put("code", ConstentUtil.UNKNOW_ERROR_CODE);
            ret.put("error", ConstentUtil.UNKNOW_ERROR);
            ret.put("detail", ConstentUtil.UNKNOW_ERROR_DETAIL);
            ret.put("data", new JSONObject());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject new_missing_args_json(){
        JSONObject ret = new JSONObject();
        try {
            ret.put("code", ConstentUtil.MISSING_ARGS_CODE);
            ret.put("error", ConstentUtil.MISSING_ARGS);
            ret.put("detail", ConstentUtil.MISSING_ARGS_DETAIL);
            ret.put("data", new JSONObject());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject new_register_failed_json(String msg){
        JSONObject ret = new JSONObject();
        try {
            ret.put("code", ConstentUtil.REGISTRATION_FAILED_CODE);
            ret.put("error", ConstentUtil.REGISTRATION_FAILED);
            ret.put("detail", msg);
            ret.put("data", new JSONObject());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject new_recognition_failed_json(String msg){
        JSONObject ret = new JSONObject();
        try {
            ret.put("code", ConstentUtil.REGISTRATION_FAILED_CODE);
            ret.put("error", ConstentUtil.REGISTRATION_FAILED);
            ret.put("detail", msg);
            ret.put("data", new JSONObject());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject new_success_json(){
        JSONObject ret = new JSONObject();
        try {
            ret.put("code", ConstentUtil.SUCCESS_CODE);
            ret.put("error", ConstentUtil.SUCCESS);
            ret.put("detail", ConstentUtil.SUCCESS_DETAIL);
            ret.put("data", new JSONObject());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject handle_register_user(IHTTPSession session){
        JSONObject ret = new_register_failed_json(ConstentUtil.REGISTRATION_FAILED_DETAIL);
        try {
            Map<String, String> files = new HashMap<String, String>();
            session.parseBody(files);
            Map<String, String> parms = session.getParms();
            Log.e(TAG, "handle_register_user parms " + parms + " files " + files);
            if(files.get("image") == null || parms.get("name") == null ||
                    parms.get("user_id") == null || parms.get("remark") == null){
                ret = new_missing_args_json();
                return ret;
            }

            File file = new File(files.get("image"));
            byte[] image_data = new byte[(int)file.length()];
            BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
            buf.read(image_data, 0, image_data.length);
            buf.close();
            Log.e(TAG, "handle_register_user parms image_data.length " + image_data.length);

            int[][] face_region = new int[max_face_num][4];
            float[][] feature = new float[max_face_num][feature_length];
            long[] code_ret = new long[1];
            loadLibraryModule.recognition_face(image_data, face_region, feature, 1, 0, code_ret);
            if(code_ret[0] == 1000){
                String feature_str = "";
                for(int kk = 0; kk < feature_length; kk++){
                    feature_str += String.valueOf(feature[0][kk]) +",";
                    if(kk < 5) Log.e(TAG, "handle_register_user " + String.valueOf(feature[0][kk]));
                }
                String name = parms.get("name");
                String user_id = parms.get("user_id");
                String remark = parms.get("remark");
                Long feature_id = userFeatureDB.addUserFeature(name, feature_str, user_id, remark, image_data);
                if(feature_id < 0){
                    ret = new_register_failed_json(ConstentUtil.REGISTRATION_FAILED_DETAIL);
                } else {
                    ret = new_success_json();
                    query_user_feature();
                }
            } else {
                Log.e(TAG, "handle_register_user failed code: " + code_ret[0]);
                ret = new_register_failed_json(ConstentUtil.REGISTRATION_FAILED_DETAIL);
            }
        } catch (ResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject handle_recognition_user(IHTTPSession session){
        JSONObject ret = new_recognition_failed_json(ConstentUtil.REGISTRATION_FAILED_DETAIL);
        try {
            long startTime = System.currentTimeMillis();
            Map<String, String> files = new HashMap<String, String>();
            session.parseBody(files);
            Map<String, String> parms = session.getParms();
            Log.e(TAG, "handle_recognition_user parms " + parms + " files " + files + " "
                    + (System.currentTimeMillis() - startTime));
            if(files.get("image") == null){
                ret = new_missing_args_json();
                return ret;
            }
            File file = new File(files.get("image"));
            byte[] image_data = new byte[(int)file.length()];
            BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
            buf.read(image_data, 0, image_data.length);
            buf.close();
            /*
            Integer contentLength = Integer.parseInt(session.getHeaders().get( "content-length" ));
            contentLength = 406154;
            byte[] buf = new byte[contentLength];
            session.getInputStream().read( buf, 0, contentLength );
            byte[] image_data = null;
            Log.e(TAG, new String(Arrays.copyOfRange(buf, 0, 300)));
            if(1>0) return ret; */

            int[][] face_region = new int[max_face_num][4];
            float[][] feature = new float[max_face_num][feature_length];
            long[] code_ret = new long[1];
            int face_count = loadLibraryModule.recognition_face(image_data, face_region, feature, 0, 0, code_ret);
            if(code_ret[0] == 1000){
                List<Map<String, Object>> user_info = recognition_user_feature(face_count, feature);
                if(user_info.size()  == 0){
                    ret = new_recognition_failed_json(ConstentUtil.REGISTRATION_FAILED_DETAIL);
                } else {
                    JSONArray recognition_result = new JSONArray();
                    for(int i = 0; i < user_info.size(); i++){
                        Map<String, Object> user_info_tmp = user_info.get(i);
                        JSONObject recognition_user = new JSONObject();
                        recognition_user.put("feature_id", user_info_tmp.get("feature_id"));
                        recognition_user.put("name", user_info_tmp.get("name"));
                        recognition_user.put("picture_id", user_info_tmp.get("picture_id"));
                        recognition_user.put("user_id", user_info_tmp.get("user_id"));
                        recognition_user.put("remark", user_info_tmp.get("remark"));
                        JSONArray location = new JSONArray();
                        for(int j = 0; j < 4; j++) {
                            location.put(face_region[(int)user_info_tmp.get("index")][j]);
                        }
                        recognition_user.put("location", location);
                        recognition_result.put(recognition_user);
                    }
                    ret = new_success_json();
                    ret.put("data", recognition_result);
                }
            } else {
                ret = new_recognition_failed_json(ConstentUtil.REGISTRATION_FAILED_DETAIL);
            }

        } catch (ResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject handle_update_user_info(IHTTPSession session){
        JSONObject ret = new_unknow_error_json();
        try {
            Map<String, String> files = new HashMap<String, String>();
            session.parseBody(files);
            Map<String, String> parms = session.getParms();
            Log.e(TAG, "handle_update_user_info parms " + parms + " files " + files);
            if(parms.get("feature_id") == null){
                ret = new_missing_args_json();
                return ret;
            }

            int feature_id = Integer.valueOf(parms.get("feature_id"));
            String feature_str = ""; //(String)user_info.get("feature");
            int picture_id_old = -1;
            byte[] image_data = null;
            Map<String, Object> user_info = feature_id_to_feature.get(feature_id);
            if(user_info == null){
                ret = new_missing_args_json();
                return ret;
            }

            if(files.get("image") != null) {
                File file = new File(files.get("image"));
                image_data = new byte[(int) file.length()];
                BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
                buf.read(image_data, 0, image_data.length);
                buf.close();

                int[][] face_region = new int[max_face_num][4];
                float[][] feature = new float[max_face_num][feature_length];
                long[] code_ret = new long[1];
                loadLibraryModule.recognition_face(image_data, face_region, feature, 1, 0, code_ret);
                if (code_ret[0] == 1000) {
                    for (int kk = 0; kk < feature_length; kk++) {
                        feature_str += String.valueOf(feature[0][kk]) + ",";
                    }
                    picture_id_old = (int)user_info.get("picture_id");
                } else {
                    return ret;
                }
            } else{
                //feature_str = "";
            }

            String name = parms.get("name");
            String user_id = parms.get("user_id");
            String remark = parms.get("remark");
            if(name == null || user_id == null || remark == null){
                ret = new_missing_args_json();
                return ret;
            }
            Long feature_id_tmp = userFeatureDB.updateUserFeature(feature_id, name, feature_str, user_id, remark,
                    image_data, picture_id_old);
            if (feature_id_tmp < 0) {
            } else {
                ret = new_success_json();
                query_user_feature();
            }
        } catch (ResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject handle_delete_user(IHTTPSession session){
        JSONObject ret = new_unknow_error_json();
        try {
            Map<String, String> files = new HashMap<String, String>();
            session.parseBody(files);
            Map<String, String> parms = session.getParms();
            Log.e(TAG, "handle_delete_user parms " + parms + " files " + files);
            if(parms.get("feature_id") == null){
                ret = new_missing_args_json();
                return ret;
            }

            int feature_id = Integer.valueOf(parms.get("feature_id"));
            Map<String, Object> user_info = feature_id_to_feature.get(feature_id);
            if(user_info == null){
                ret = new_missing_args_json();
                return ret;
            }
            int picture_id_old = (int)user_info.get("picture_id");
            boolean delete_result = userFeatureDB.deleteUserFeatureById(feature_id, picture_id_old);
            if (delete_result) {
                ret = new_success_json();
                query_user_feature();
            } else {
            }
        } catch (ResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject handle_get_all_user(IHTTPSession session){
        long startTime = System.currentTimeMillis();
        JSONObject ret = new_unknow_error_json();
        try {
            Map<String, String> files = new HashMap<String, String>();
            session.parseBody(files);
            Map<String, String> parms = session.getParms();
            Log.e(TAG, "handle_get_all_user parms " + parms + " files " + files);
            Log.e(TAG, "handle_get_all_user 1 " + (System.currentTimeMillis() - startTime));

            JSONArray all_user_info = new JSONArray();
            for(int i = 0; i < all_user_feature.size(); i++){
                Map<String, Object> user_info_tmp = all_user_feature.get(i);
                JSONObject user_info = new JSONObject();
                user_info.put("feature_id", user_info_tmp.get("feature_id"));
                user_info.put("name", user_info_tmp.get("name"));
                user_info.put("picture_id", user_info_tmp.get("picture_id"));
                user_info.put("user_id", user_info_tmp.get("user_id"));
                user_info.put("remark", user_info_tmp.get("remark"));
                all_user_info.put(user_info);
            }
            ret = new_success_json();
            ret.put("data", all_user_info);
            return ret;
        } catch (ResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    JSONObject handle_get_person_head_picture(IHTTPSession session){
        JSONObject ret = new_unknow_error_json();
        try {
            Map<String, String> files = new HashMap<String, String>();
            session.parseBody(files);
            Map<String, String> parms = session.getParms();
            Log.e(TAG, "handle_get_person_head_picture parms " + parms + " files " + files);
            if(parms.get("picture_id") == null || parms.get("feature_id") == null){
                ret = new_missing_args_json();
                return ret;
            }
            int feature_id = Integer.valueOf(parms.get("feature_id"));
            int picture_id = Integer.valueOf(parms.get("picture_id"));
            Map<String, Object> user_info = feature_id_to_feature.get(feature_id);
            if((int)user_info.get("picture_id") != picture_id){
                ret = new_missing_args_json();
                return ret;
            }
            ret = new_success_json();
            byte[] image = userFeatureDB.queryUserPicture(picture_id);
            ret.put("data", image);
        } catch (ResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return ret;
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        long startTime = System.currentTimeMillis();
        JSONObject ret = new_unknow_error_json();
        if(uri.equals("/register_user")) {
            ret = handle_register_user(session);
        } else if(uri.equals("/recognition_user")){
            ret = handle_recognition_user(session);
        } else if(uri.equals("/update_user_info")){
            ret = handle_update_user_info(session);
        } else if(uri.equals("/delete_user")){
            ret = handle_delete_user(session);
        } else if(uri.equals("/get_all_user")){
            ret = handle_get_all_user(session);
        } else if(uri.equals("/get_person_head_picture")){
            ret = handle_get_person_head_picture(session);
            byte[] image = null;
            try {
                image = (byte[]) ret.get("data");
            } catch (JSONException e){
                e.printStackTrace();
                ret = new_unknow_error_json();
                return newFixedLengthResponse(Response.Status.OK, "application/json;charset=UTF-8", ret.toString());
            }
            InputStream in = new ByteArrayInputStream(image);
            return newFixedLengthResponse(Response.Status.OK, "image/jpeg", in, image.length);
        } else {
            Log.e(TAG, "uri not found " + uri);
        }
        Log.e(TAG, uri + " thread_id " + Thread.currentThread().getId() +
                " total time: " + (System.currentTimeMillis() - startTime) + " ms" + ret.toString());
        return newFixedLengthResponse(Response.Status.OK, "application/json;charset=UTF-8", ret.toString());
    }
}
